package repository

import (
	"errors"
	"gitee.com/fierce_wolf/go-fox-edge-common/commEntityManager"
	"gitee.com/fierce_wolf/go-fox-edge-common/commEnv"
	"gitee.com/fierce_wolf/go-fox-edge-common/commUtil/FileCompare"
	"gitee.com/fierce_wolf/go-fox-edge-common/commUtil/FileName"
	"gitee.com/fierce_wolf/go-fox-edge-common/commUtil/HttpClient"
	"gitee.com/fierce_wolf/go-fox-edge-common/commUtil/Json"
	"gitee.com/fierce_wolf/go-fox-edge-common/commUtil/Map"
	"gitee.com/fierce_wolf/go-fox-edge-common/commUtil/Method"
	"gitee.com/fierce_wolf/go-fox-edge-common/commUtil/OS"
	"gitee.com/fierce_wolf/go-fox-edge-common/commUtil/OS/ShellUtils"
	"gitee.com/fierce_wolf/go-fox-edge-common/commUtil/Slice"
	"gitee.com/fierce_wolf/go-fox-edge-common/commUtil/String"
	"gitee.com/fierce_wolf/go-fox-edge-common/edgeEnv"
	"go-fox-edge-kernel-manager/component"
	"go-fox-edge-kernel-manager/utils/ProcessUtils"
	"strconv"
	"strings"
)

type repoCloudFileInstallService struct {
}

func (e *repoCloudFileInstallService) QueryUriListFile(modelType string) ([]map[string]interface{}, error) {
	body := make(map[string]interface{})
	body["modelType"] = modelType

	if modelType == "service" {
		body["workMode"] = edgeEnv.GetWorkMode()
	}

	respond, err := CloudRemoteService.queryCloudCompFileList(body)
	if err != nil {
		return nil, err
	}

	list := respond["data"]
	if list == nil {
		return nil, errors.New("云端数据仓库返回的数据为空")
	}

	return Slice.ToMapList(list.([]interface{})), nil
}

func (e *repoCloudFileInstallService) ExtendLocalStatus(entity map[string]interface{}) {
	modelType := Map.GetString(entity, "modelType", "")
	modelName := Map.GetString(entity, "modelName", "")
	lastVersion := Map.GetMap(entity, "lastVersion", make(map[string]interface{}))
	versions := Map.GetSlice(entity, "versions", make([]interface{}, 0))
	component := Map.GetString(entity, "component", "")

	// 验证last版本的破损状态
	status := CloudFileInstallStatus.VerifyMd5Status(modelType, modelName, component, lastVersion)
	if RepoStatusConstant.DamagedPackage == status {
		lastVersion["status"] = status
	} else {
		if CloudFileInstallStatus.verifyUpgradeStatus(modelType, modelName, lastVersion, versions) {
			lastVersion["status"] = RepoStatusConstant.NeedUpgrade
		}
	}

	// 验证明细包的破损状态
	for _, v := range versions {
		verEntity := v.(map[string]interface{})

		status = CloudFileInstallStatus.VerifyMd5Status(modelType, modelName, component, verEntity)
		verEntity["status"] = status

		// 检查：该版本是否为【已安装】版本，如果是，则该版本为当前版本，因为【已安装】版本为正在使用的唯一版本
		if RepoStatusConstant.Installed == status {
			entity["usedVersion"] = verEntity
		}

	}
}

func (e *repoCloudFileInstallService) InsertRepoCompEntity(modelType, modelName string) error {
	// 向云端查询组件信息
	body := make(map[string]interface{})
	body["modelType"] = modelType
	body["modelName"] = modelName

	respond, err := CloudRemoteService.queryCloudCompFileList(body)
	if err != nil {
		return err
	}

	// 取出组件信息
	list := Map.GetSlice(respond, "data", make([]interface{}, 0))
	if Method.HasEmpty(list) {
		return errors.New("云端数据仓库返回的数据为空！")
	}

	data := list[0].(map[string]interface{})

	// 构造本地仓库组件实体需要的参数
	localMap, err := LocalCompConvert.ConvertCloud2Local(data)
	if err != nil {
		return err
	}
	if Method.HasEmpty(localMap) {
		return nil
	}

	// 构造本地仓库实体
	repoCompEntity, err := LocalCompBuilder.BuildCompEntity(localMap)
	if err != nil {
		return err
	}
	if repoCompEntity == nil {
		return errors.New("构造的本地仓库组件实体为null！")
	}

	// 插入、更新数据
	exist, err := commEntityManager.GetEntityByServiceKey(repoCompEntity.Type(), repoCompEntity.MakeServiceKey())
	if err != nil {
		return err
	}

	if exist == nil {
		commEntityManager.InsertEntity(repoCompEntity)
	} else {
		clone := repoCompEntity.Instance()

		err = Json.Clone(exist, clone)
		if err != nil {
			return err
		}

		Map.Append(clone.CompParam, repoCompEntity.CompParam)
		commEntityManager.UpdateEntity(clone)
	}

	return nil
}

func (e *repoCloudFileInstallService) InstallFile(modelType, modelName, version, stage, component string) error {
	// 简单验证
	if Method.HasEmpty(modelType, modelName, version, stage, component) {
		return errors.New("参数不能为空:modelType, modelName, version, stage, component")
	}

	tarDir := LocalPathName.getPathName4LocalRepo2tar(modelType, modelName, version, stage, component)

	// 备份目标文件，然后将解压文件复制复制到目标目录
	if modelType == "decoder" {
		e.installDecoderFile(tarDir, FileName.GetOsFilePath(commEnv.Service.WorkDir+"/jar/decoder"), modelName)

		// 注册为加载
		jarFileName := modelName + ".jar"
		LocalJarFileConfig.UpdateConfig(jarFileName, true)
	}
	if modelType == "service" {
		if "kernel" == component || "system" == component || "service" == component {
			// 安装文件
			e.installServiceFile(modelName, component, version, stage)

			// 扫描service.conf文件信息，并注册为组件信息
			LocalApplication.updateRepoCompEntity(component, modelName)

			// 配置为随机启动
			LocalAppLoad.SaveServiceLoad(modelName, component, true)
		} else {
			return errors.New("component必须为service或者system或者kernel")
		}
	}
	if modelType == "webpack" {
		e.installWebpackFile(tarDir, commEnv.Service.WorkDir)
	}

	// 安装某个文件版本后，其他文件版本的状态，都会联动变化，所以要扫描整个大版本
	versions := LocalPathName.findRepoLocalModelName(modelType, modelName)
	for _, data := range versions {
		subVersion := Map.GetString(data, "version", "")
		subStage := Map.GetString(data, "stage", "")
		subComponent := Map.GetString(data, "component", "")

		// 将状态保存起来
		CloudFileInstallStatus.scanLocalStatusComponent(modelType, modelName, subVersion, subStage, subComponent)
		CloudFileInstallStatus.scanLocalMd5Component(modelType, modelName, subVersion, subStage, subComponent)
	}

	return nil
}

func (e *repoCloudFileInstallService) mkdirDir(destFileDir string) {
	destFileDir = FileName.GetOsFilePath(destFileDir)

	if OS.Type.IsWindows() {
		ShellUtils.ExecuteCmd("mkdir " + destFileDir)
	}
	if OS.Type.IsLinux() {
		ShellUtils.ExecuteShell("mkdir -p '" + destFileDir + "'")
	}
}

func (e *repoCloudFileInstallService) copyFile(installFileDir, fileName, destFileDir, destFileName string) {
	installFileDir = FileName.GetOsFilePath(installFileDir)
	destFileDir = FileName.GetOsFilePath(destFileDir)

	if OS.Type.IsWindows() {
		ShellUtils.ExecuteCmd("copy /y \"" + installFileDir + "\\" + fileName + "\" \"" + destFileDir + "\\" + destFileName + "\"")
	}
	if OS.Type.IsLinux() {
		ShellUtils.ExecuteShell("cp -f '" + installFileDir + "/" + fileName + "' '" + destFileDir + "/" + destFileName + "'")
	}
}

func (e *repoCloudFileInstallService) installDecoderFile(installFileDir, destFileDir, modelName string) error {
	// 获得repository目录下所有下的可安装所有文件名
	fileList := FileName.ListFile(installFileDir, false, false)
	if Method.HasEmpty(fileList) {
		return errors.New(installFileDir + " 没有文件")
	}

	// 预创建目录
	e.mkdirDir(destFileDir)

	// 复制文件：该目录实际上有且只有一个jar文件
	for _, fileName := range fileList {
		if !strings.HasSuffix(fileName, ".jar") {
			continue
		}

		destFileName := modelName + ".jar"

		// 检查：该文件是否已经存在，如果存在，那么就检查文件内容是否相同
		if FileName.IsFile(FileName.GetOsFilePath(destFileDir + "/" + destFileName)) {
			// 检查：两个文件是否一致，如果一致旧不需重复替换
			same, err := FileCompare.IsSameFile(installFileDir+"/"+fileName, destFileDir+"/"+destFileName)
			if err != nil {
				return err
			}
			if same {
				continue
			}
		}

		// 拷贝文件
		e.copyFile(installFileDir, fileName, destFileDir, destFileName)
	}

	return nil
}

func (e *repoCloudFileInstallService) installServiceFile(modelName, component, version, stage string) error {
	// 管理服务默认固定9000端口，其他服务按动态端口分配
	serverPort := 9000
	if "gateway-service" != modelName || "kernel" != component {
		val, err := LocalAppPort.GetServicePort(modelName, component)
		if err != nil {
			return err
		}

		serverPort = val
	}

	ShellUtils.ExecuteShell(commEnv.Service.WorkDir + "/shell/upgrade.sh " + component + " " + modelName + " " + version + " " + stage + " " + String.ToString(serverPort))

	return nil
}

func (e *repoCloudFileInstallService) installWebpackFile(installFileDir, destFileDir string) {
	installFileDir = FileName.GetOsFilePath(installFileDir)
	destFileDir = FileName.GetOsFilePath(destFileDir)

	if OS.Type.IsWindows() {
		// 删除旧文件
		ShellUtils.ExecuteCmd("rd /s /q " + destFileDir + "\\dist")
		// 复制新文件
		ShellUtils.ExecuteCmd("xcopy /s/r/Y/F/q " + installFileDir + " " + destFileDir)
	}
	if OS.Type.IsLinux() {
		ShellUtils.ExecuteShell("cp -rf '" + installFileDir + "/dist' '" + destFileDir + "'")
	}
}

func (e *repoCloudFileInstallService) deleteEmptyDir(fileDir string) {
	fileDir = FileName.GetOsFilePath(fileDir)

	// 是否为空目录
	list, err := FileName.List(fileDir)
	if err != nil {
		return
	}
	if len(list) != 0 {
		return
	}

	if OS.Type.IsWindows() {
		// 删除可能存在的目录
		ShellUtils.ExecuteCmd("rd /s /q " + fileDir)
	}
	if OS.Type.IsLinux() {
		ShellUtils.ExecuteShell("rm -rf '" + fileDir + "'")
	}
}

func (e *repoCloudFileInstallService) DeletePackageFile(modelType, modelName, version, stage, component string) error {
	if Method.HasEmpty(modelType, modelName, version, component) {
		return errors.New("参数不能为空: modelType, modelName, version, component")
	}

	modelNameDir := LocalPathName.getPathName4LocalRepo2modelName(modelType, modelName)
	modelVersionDir := modelNameDir
	packageDir := modelVersionDir + "/" + version

	packageDir = FileName.GetOsFilePath(packageDir)

	//  删除安装包文件
	// windows版本：不能查询进程，返回个空集合
	if OS.Type.IsWindows() {
		ShellUtils.ExecuteCmd("rd /s /q " + packageDir)
	}
	if OS.Type.IsLinux() {
		ShellUtils.ExecuteShell("rm -rf '" + packageDir + "'")
	}

	// 检查：是否整个模块都清空了，如果是就删除残余目录
	e.deleteEmptyDir(modelVersionDir)
	e.deleteEmptyDir(modelNameDir)

	// 将状态保存起来
	CloudFileInstallStatus.scanLocalStatusComponent(modelType, modelName, version, stage, component)
	CloudFileInstallStatus.scanLocalMd5Component(modelType, modelName, version, stage, component)

	return nil
}

func (e *repoCloudFileInstallService) TestUrlFileCanBeOpen(modelType, modelName, version, pathName string) error {
	if Method.HasEmpty(modelType, modelName, version, pathName) {
		return errors.New("参数不能为空: modelType, modelName, version, pathName")
	}

	configValue, err := component.ManageConfigService.GetConfigValueThis("repositoryConfig")
	if err != nil {
		return err
	}

	host := Map.GetString(configValue, "file", "http://www.fox-tech.cn")
	if Method.HasEmpty(host) {
		return errors.New("尚未配置仓库的uri，请先配置仓库的uri")
	}

	urlStr := host + "/" + modelType + "/" + modelName + "/" + "v1" + "/" + version + "/" + pathName

	return HttpClient.TestUrlFileCanBeOpen(urlStr)
}

func (e *repoCloudFileInstallService) downloadFile(modelType, modelName, version, stage, pathName, comp string) error {
	if Method.HasEmpty(modelType, modelName, version, stage, pathName, comp) {
		return errors.New("参数不能为空: modelType, modelName, version, stage, pathName, component")
	}

	// 根据线上的文件扩展名，确定本地的文件扩展名
	fileName := ""
	if strings.HasSuffix(pathName, ".tar") {
		fileName = LocalPathName.getFileName4LocalRepoTarFile(modelName, version, ".tar")
	}
	if strings.HasSuffix(pathName, ".gz") {
		fileName = LocalPathName.getFileName4LocalRepoTarFile(modelName, version, ".tar.gz")
	}
	if fileName == "" {
		return errors.New("参数不能为空: 不支持的文件压缩包格式")
	}

	configValue, err := component.ManageConfigService.GetConfigValueThis("repositoryConfig")
	if err != nil {
		return err
	}

	host := Map.GetString(configValue, "file", "http://www.fox-tech.cn")
	if Method.HasEmpty(host) {
		return errors.New("参数不能为空: host")
	}

	if !strings.HasSuffix(fileName, ".tar") && !strings.HasSuffix(fileName, ".tar.gz") {
		return errors.New("文件必须为tar或者gz格式")
	}

	components := make([]string, 0)
	components = append(components, "bin")
	components = append(components, "template")
	components = append(components, "service")
	components = append(components, "system")
	components = append(components, "kernel")
	components = append(components, "webpack")
	if !Slice.Has(components, comp) {
		return errors.New("component必须为:" + comp)
	}

	// 下载tar文件
	url := host + "/" + modelType + "/" + modelName + "/" + "v1" + "/" + version + "/" + pathName
	localPath := LocalPathName.getPathName4LocalRepo2component(modelType, modelName, version, stage, comp)
	HttpClient.DownloadFile(url, localPath, fileName)

	tarDir := localPath + "/" + "tar"
	localFile := localPath + "/" + fileName

	tarDir = FileName.GetOsFilePath(tarDir)
	localFile = FileName.GetOsFilePath(localFile)

	//  删除安装包文件
	// windows版本：不能查询进程，返回个空集合
	if OS.Type.IsWindows() {
		// 删除已经存在的解压目录
		ShellUtils.ExecuteCmd("rd /s /q " + tarDir)

		// 新建一个目解压录
		ShellUtils.ExecuteCmd("mkdir " + tarDir)

		// 解压文件到解压目录:tar -xf 静默解压，windows下不能返回太多内容，否则会死锁
		ShellUtils.ExecuteCmd("tar -xf " + localFile + " -C " + tarDir)
	}
	if OS.Type.IsLinux() {
		// 删除已经存在的解压目录
		ShellUtils.ExecuteShell("rm -r  '" + tarDir + "'")

		// 新建一个目解压录
		ShellUtils.ExecuteShell("mkdir '" + tarDir + "'")

		// 解压文件到解压目录
		ShellUtils.ExecuteShell("tar -xvf '" + localFile + "' -C '" + tarDir + "'")
	}

	// 将状态保存起来
	CloudFileInstallStatus.scanLocalStatusComponent(modelType, modelName, version, stage, comp)
	CloudFileInstallStatus.scanLocalMd5Component(modelType, modelName, version, stage, comp)

	return nil
}

func (e *repoCloudFileInstallService) UninstallServiceFile(appName, appType string) error {
	if Method.HasEmpty(appName, appType) {
		return errors.New("参数不能为空: appName, appType")
	}

	shellFileInfoList := LocalAppConf.GetConfFileInfoList()
	ProcessUtils.ExtendAppStatus(shellFileInfoList)

	for _, data := range shellFileInfoList {
		if appName != Map.GetString(data, "appName", "") {
			continue
		}
		if appType != Map.GetString(data, "appType", "") {
			continue
		}
		if appType == "kernel" {
			continue
		}

		// 获得进程的PID，如果为空，则说明该进程并没有启动
		pid := Map.GetInt(data, "pid", 0)
		if pid != 0 {
			// kill掉该进程
			ShellUtils.ExecuteShell("kill -9 " + strconv.Itoa(pid))
		}

		// 检查：文件的合法性
		fileName := Map.GetString(data, "fileName", "")
		if Method.HasEmpty(fileName) {
			continue
		}

		binDir := FileName.GetOsFilePath(commEnv.Service.WorkDir + "/bin/" + appType + "/" + appName)
		ShellUtils.ExecuteShell("rm -r  '" + binDir + "'")

		// 删除shell目录
		shellDir := FileName.GetOsFilePath(commEnv.Service.WorkDir + "/shell/" + appType + "/" + appName)
		ShellUtils.ExecuteShell("rm -r  '" + shellDir + "'")

		// 删除conf目录
		confDir := FileName.GetOsFilePath(commEnv.Service.WorkDir + "/conf/" + appType + "/" + appName)
		ShellUtils.ExecuteShell("rm -r  '" + confDir + "'")

		// 删除temp目录
		tempDir := FileName.GetOsFilePath(commEnv.Service.WorkDir + "/temp/" + appType + "/" + appName)
		ShellUtils.ExecuteShell("rm -r  '" + tempDir + "'")
	}

	// 获得跟该应用相关的各版本信息
	val, _ := CloudFileInstallStatus.mapGetValue("service", appName)
	if Method.HasEmpty(val) {
		return nil
	}

	versions := val.(map[string]interface{})

	// 重新扫描各版本的安装状态：因为安装后是不知道具体哪个版本，所以要扫描各个版本
	for version, v := range versions {
		stages := v.(map[string]interface{})

		for stage, vs := range stages {
			components := vs.(map[string]interface{})

			for component, _ := range components {
				CloudFileInstallStatus.scanLocalStatusComponent("service", appName, version, stage, component)
				CloudFileInstallStatus.scanLocalMd5Component("service", appName, version, stage, component)

			}
		}
	}

	return nil
}
